#include "xbench/colorprint.hpp"
#include <map>
#include "xbench/benchmark.hpp"

using namespace std;

const xbench::Color xbench::Color::Red = xbench::Color(1);
const xbench::Color xbench::Color::Green = xbench::Color(2);
const xbench::Color xbench::Color::Blue = xbench::Color(4);
const xbench::Color xbench::Color::Yellow = xbench::Color(3);
const xbench::Color xbench::Color::White = xbench::Color(7);

/**
 * xbench::Color
 * */
ostream& xbench::operator<<(ostream &os, const xbench::Color &color){
#ifdef WIN32
    using xbench::Color;
    static const map<int, int> color_table = {
            {Color::Red._value, 0xc},
            {Color::Green._value, 0xa},
            {Color::Blue._value, 0x9},
            {Color::Yellow._value, 0x6},
            {Color::White._value, 0x7},
    };

    for(auto& [k, v]: color_table){
        if(color._value == k){
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), v);
        }
    }
#else
    os << "\033[" << (30 + color._value) << "m";
#endif
    return os;
}



/**
 * xbench::StateTable
 */
xbench::ColorPrint::ColorPrint(xbench::StateTable table) {
    this->_total_nanoseconds = static_cast<double>(table.total_nanoseconds());
    this->_table = std::move(table);
    this->_name_length = this->get_max_length();
}

void xbench::ColorPrint::print_state(ostream &os, const xbench::State *state) const{
    auto& name_length = this->_name_length;
    auto& calls_length = xbench::ColorPrint::_calls_length;
    auto& duration_length = xbench::ColorPrint::_duration_length;
    auto& ratio_length = xbench::ColorPrint::_ratio_length;
    auto& total_nanoseconds = this->_total_nanoseconds;

    const auto& name = state->name();
    auto duration_ns = state->duration_ns();
    auto calls = state->calls();
    double ratio = 0.0;

    if(total_nanoseconds > 0)
        ratio = (static_cast<double>(duration_ns) / total_nanoseconds * 100);

    // Name
    os << "| " << left << xbench::Color::Green << setw(name_length) << name << xbench::Color::White << "|";
    // _duration_ns
    os << right << xbench::Color::Yellow << setw(duration_length - 3);
    constexpr int64_t n = 1000 * 1000 * 1000;
    constexpr int64_t m = 1000 * 1000;

    if(duration_ns < n)
        os << duration_ns << " ns";
    else
        os << static_cast<int64_t>(duration_ns / m) << " ms";
    // Ratio
    os << right << xbench::Color::Blue << setw(ratio_length - 1) << fixed << setprecision(3) << ratio << "%";
    // Calls
    os << right << setw(calls_length) << calls << xbench::Color::White << " |" << endl;
}

ostream &xbench::operator<<(ostream &os, const xbench::ColorPrint &color_print) {
    const auto& table = color_print._table;
    auto& name_length = color_print._name_length;
    auto& calls_length = xbench::ColorPrint::_calls_length;
    auto& duration_length = xbench::ColorPrint::_duration_length;
    auto& ratio_length = xbench::ColorPrint::_ratio_length;
    auto& total_nanoseconds = color_print._total_nanoseconds;
    auto line = xbench::ColorPrint::str_line(
            name_length + duration_length + calls_length + ratio_length + 5);

    // Line
    os << line << endl;
    // Title
    os << left  << "| " << setw(name_length) << "State Name" << "|";
    os << right << setw(duration_length) << "Duration";
    os << right << setw(ratio_length) << "Ratio";
    os << right << setw(calls_length) << "Calls"  << " |" << endl;
    // Line
    os << line << endl;

    // Data
    for(const auto state: color_print._table.get()){
        color_print.print_state(os, state);
    }

    os << xbench::Color::White << line << endl;
    return os;
}

int xbench::ColorPrint::get_max_length() const {
    int result = xbench::ColorPrint::_min_name_length;
    for(const auto state: this->_table.get())
        result = max(result, static_cast<int>(state->name().length()));
    return result;
}

string xbench::ColorPrint::str_line(int size) {
    string result;
    result.resize(size);
    result.clear();

    for(auto i = 0; i < size; i++){
        result.push_back('-');
    }
    return result;
}
